//
//  PMBody.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 10/28/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

/**
 * A rigid body. PMBody objects describe and maintain a physical state in the 
 * physics space. The state (position, velocity, etc.) of a PMBody within a PMSpace 
 * will be affected by gravity. PMBody objects do not, however, handle 
 * any kind of collision. In order to have PMBody states altered by collisions,
 * you must use the PMBody with a corresponding PMShape object. For more information, 
 * see the PMShape class. PMBodys created for use exclusively with static PMShapes
 * should be initialized with mass and moment of inertia set to infinity, this
 * can be easily accomplished by using the staticBody convenience constructor.
 */
#pragma mark TODO: Make infinite body singleton?

#import "PMBody.h"


// Singleton rigid body used for static shapes
static PMBody *staticBody = nil;

@implementation PMBody

@synthesize data;


#pragma mark -
#pragma mark Init

/**
 * Returns a PMBody object initialized with infinite mass and moment of inertia. 
 * This method should be used to initialize PMBodys to be used exclusively with 
 * static PMShapes.
 */
- (id)init {
	return [self initWithMass:PMInfinity Moment:PMInfinity];
}

/**
 * Returns a PMBody object initialized with a given mass and moment of inertia.
 */
- (id)initWithMass:(PMFloat)mass Moment:(PMFloat)moment {
	if (![super init])
		return nil;
	
	// allocate and initialize cpBody
	body = cpBodyNew(mass, moment);
	
	return self;
}

/**
 * Creates and returns a singleton PMBody object initialized with infinite mass 
 * and moment of inertia. This singleton instance should be used with static
 * collision shapes.
 */
+ (PMBody *)staticBody {
	// instantiate?
	if (!staticBody)
		staticBody = [[PMBody alloc] init];
		
	// verify
	if ([staticBody mass] < PMInfinity)
		[staticBody setMass:PMInfinity];
	if ([staticBody moment] < PMInfinity)
		[staticBody setMoment:PMInfinity];
					  
	return staticBody;
}


#pragma mark -
#pragma mark Dealloc

/**
 * Releases member objects and frees memory used for cpBody before deallocating.
 */
- (void)dealloc {
	[self.data release];
	cpBodyFree(body);
	[super dealloc];
}


#pragma mark -
#pragma mark Getters

/**
 * Returns the receiver's cpBody reference. This is not intended for external use.
 */
- (cpBody *)cpBody {
	return body;
}

/**
 * Returns the receiver's mass.
 */
- (PMFloat)mass {
	return body->m;
}

/**
 * Returns the inverse of the receiver's mass.
 */
- (PMFloat)massInverse {
	return body->m_inv;
}

/**
 * Returns the receiver's moment of iniertia.
 */
- (PMFloat)moment {
	return body->i;
}

/**
 * Returns the inverse of the receiver's moment of iniertia.
 */
- (PMFloat)momentInverse {
	return body->i_inv;
}

/**
 * Returns the receiver's position vector.
 */
- (PMVect)position {
	return (PMVect) body->p;
}

/**
 * Returns the receiver's velocity vector.
 */
- (PMVect)velocity {
	return (PMVect) body->v;
};
/**
 * Returns the receiver's net force vector.
 */
- (PMVect)force {
	return (PMVect) body->f;
}

/**
 * Returns the receiver's angular position in radians.
 */
- (PMFloat)angle {
	return body->a;
}

/**
 * Returns the receiver's rotation vector. 
 */
- (PMVect)rotationVector {
	return (PMVect) body->rot;
}

/**
 * Returns the receiver's angular velocity.
 */
- (PMFloat)angularVelocity {
	return body->w;
}

/**
 * Returns the receiver's net torque.
 */
- (PMFloat)torque {
	return body->t;
}


#pragma mark -
#pragma mark Setters

/**
 * Sets the receiver's mass to a given value.
 */
- (void)setMass:(PMFloat)mass {
	cpBodySetMass(body, mass);
}

/**
 * Sets the receiver's moment of inertia to a given value.
 */
- (void)setMoment:(PMFloat)moment {
	cpBodySetMoment(body, moment);
}

/**
 * Sets the receiver's position to a given vector.
 */
- (void)setPosition:(PMVect)position {
	body->p = position;
}

/**
 * Sets the receiver's velocity to a given vector.
 */
- (void)setVelocity:(PMVect)velocity {
	body->v = velocity;
}

/**
 * Sets the receiver's net force to a given vector.
 */
- (void)setForce:(PMVect)force {
	body->f = force;
}

/**
 * Sets the receiver's angular position to a given radian value.
 */
- (void)setAngle:(PMFloat)angle {
	cpBodySetAngle(body, angle);
}

/**
 * Sets the receiver's angular velocity to a given value.
 */
- (void)setAngularVelocity:(PMFloat)angularVelocity {
	body->w = angularVelocity;
}

/**
 * Sets the receiver's net torque to a given value.
 */
- (void)setTorque:(PMFloat)torque {
	body->t = torque;
}


#pragma mark -
#pragma mark Force Application

/**
 * Applies a given impulse to the receiver at the center of gravity.
 */
- (void)applyImpulse:(PMVect)impulse {
	cpBodyApplyImpulse(body, impulse, PMVectZero);
}

/**
 * Applies a given impulse to the receiver at a given offset from the center of
 * gravity.
 */
- (void)applyImpulse:(PMVect)impulse atRelativeOffset:(PMVect)offset {
	cpBodyApplyImpulse(body, impulse, offset);
}

/**
 * Applies (accumulates) a given force to the receiver at the center of gravity.
 */
- (void)applyForce:(PMVect)force {
	cpBodyApplyForce(body, force, PMVectZero);
}

/**
 * Applies (accumulates) a given force to the receiver at a given offset from 
 * the center of gravity.
 */
- (void)applyForce:(PMVect)force atRelativeOffset:(PMVect)offset {
	cpBodyApplyForce(body, force, offset);
}

/**
 * Sets net force and torque of the receiver to zero.
 */
- (void)resetForces {
	cpBodyResetForces(body);
}

#pragma mark TO DO: applyDampedSpring:


#pragma mark -
#pragma mark Other Methods

/**
 * Modifies the receiver's velocity so that it will move to a point in a given
 * amount of time. This method is intended for movement of bodies without regard 
 * to local physics, ie. dragging.
 */
- (void)slewToPoint:(PMVect)point inTime:(PMFloat)deltaT {
	cpBodySlew(body, point, deltaT);
}

/**
 * Converts a given point from the receiver's local coordinate system to world
 * coordinates.
 */
- (PMVect)ConvertPointFromLocal:(PMVect)point {
	return (PMVect) cpBodyLocal2World(body, point);
}

/**
 * Converts a given point from world coordinates to the receiver's local coordinate
 * system.
 */
- (PMVect)ConvertPointToLocal:(PMVect)point {
	return (PMVect) cpBodyWorld2Local(body, point);
}

@end
